'''###########################################################################################################################
### File: level_gen.py
### Name: Patrick Delaney, Andrew Oberlin
### Class: CSE 487
### Instructor: Dr.Zmuda
### Assignment: Final Project
### Description: Class for handling the level generation
################ 
'''

from menu_button import MenuButton
from menu_scroller import MenuScroller
from panda3d.core import Vec2, Point3, Vec3, Vec4
from direct.gui.DirectGui import *
from level import Level
from create_level import LevelGenerator
import direct.directbase.DirectStart
from pandac.PandaModules import *
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import *
from direct.showbase.DirectObject import *
import os
import string
from objects import Object
from picker import Picker
from visual_object_3d import VisualObject
from level_file_menu_gui import LevelFileMenuGUI
from copy import deepcopy
from level_object_editing_gui import LevelObjectEditingGUI
from main_character import MainCharacter
from dialog import Dialog

class LevelGen: #Needed to make this subclass directobject in order to get the accept methods to work.
				# No you don't, just use the parent object
	'''
	### Name: __init__ (Constructor)
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: 
	'''
	def __init__(self, parent, projectDirectory,gameMaker):
		self.parent = parent
		self.models = dict()
		self.projectDirectory = projectDirectory
		self.level = None
		self.path = None
		self.gameMaker = gameMaker
		
		#Level Generator  Variables
		self.objects = []  #List of objects placeable. Used in the Level Generator Portion of project. 
		self.mainChars = []
		self.objInLevel = [] #List of the objects placed in the level.  #We may need to give an object an Identifier for deleting. So that way we don't have problems with duplicates.
		self.mainChar = None
		self.curObjIndex = 0
		self.numObjInLevel = 0
		
		self.__loadModels()
		self.parent.base.setBackgroundColor(0,0,0)
		
		self.picker = Picker(fromMask=BitMask32(0x08), collideWithGeom=True)
		self.selectPicker = Picker(fromMask=BitMask32(0x01), collideWithGeom=True)
		
	'''
	### Name: __loadModels
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: Loads the GUI objects
	'''
	def __loadModels(self):
		
		self.models['background'] = self.parent.loader.loadModelCopy("models/plane")
		self.backgroundTexture = self.parent.loader.loadTexture("models/gui/game_maker/menu_background.jpg")
		self.models['background'].setTexture(self.backgroundTexture)
		self.models['background'].setTransparency(1)
		self.models['background'].setScale(2.67, 1, 2)
		self.models['background'].reparentTo(self.parent.base.aspect2d)
		
		self.models['logo'] = self.parent.loader.loadModelCopy("models/plane")
		self.logoTexture = self.parent.loader.loadTexture("models/gui/game_maker/level_generator_logo.png")
		self.models['logo'].setTexture(self.logoTexture)
		self.models['logo'].setTransparency(1)
		self.models['logo'].setScale(2.67, 1, 0.35)
		self.models['logo'].setPos(0, 0, 0.75)
		self.models['logo'].reparentTo(self.parent.base.aspect2d)
		
		self.models['new_lvl_button'] = MenuButton(
			self.parent, 'new_lvl_button', 'models/gui/game_maker/dialog_button', 
			self.addLvl, Vec3(0, 0, 0.35), isMovie = False, text="New Level", 
			textPos=Vec3(-0.260, 0, -0.025), scale=Vec3(0.6,1,0.2)
		)
		
		numItemsVisible = 4
		itemHeight = 0.11
		
		path = "projects/" + self.projectDirectory + "/levels"  #Need to find a way to get ProjectOne name to make this universal
		self.models['existing_lvl_scroller'] = MenuScroller(
		    self.parent, 'existing_lvl_scroller', path, '.lvl', (0.0, 0.7, -0.05, 0.59), (-0.35, 0, -0.5),
			numItemsVisible,itemHeight, (-0.2, 0.2, -0.37, 0.11), (0.35, 0, 0.4), self.onClick
		)
		
		self.collisionPlane = CollisionPlane(Plane(Vec3(0, -1, 0), Point3(0,10,0))) 
		self.collisionPlaneNode = CollisionNode('planecnode')
		self.plane = base.render.attachNewNode(self.collisionPlaneNode) 
		self.plane.node().addSolid(self.collisionPlane) 
		self.collisionPlaneNode.setFromCollideMask(BitMask32(0x08))
		#self.plane.show() 
		
		self.models['file_menu_gui'] = LevelFileMenuGUI(self.parent, self.projectDirectory, self)
		self.models['file_menu_gui'].hide()
		
		self.models['back_button'] = MenuButton(self.parent, 'create_object_button', 'models/gui/game_maker/back_button', 
			self.goBack, Vec3(-.90, 0, -.80),scale=0.25, textPos=Vec3(-0.20, 0, .15), isMovie = False, text="Go Back")
		
	def addLvl(self):
		self.level = Level()
		self.levelGenerator = LevelGenerator(self.parent, self.projectDirectory, self, self.level)
		self.hide()
		
	def onClick(self,item,dir):
		self.level = Level()
		self.level.load(dir + "/" + item)
		#No need to change level name when loading?
		self.startLevelEdit()
		
	'''
	### Name: hide
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: hides this GUI
	'''
	def hide(self):
		for key in self.models.keys():
			self.models[key].hide()
			
	'''
	### Name: show
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: show this GUI
	'''
	def show(self):
		for key in self.models.keys():
			self.models[key].show()
		
		self.models['existing_lvl_scroller'].removeNode()
		path = "projects/" + self.projectDirectory + "/levels"
		numItemsVisible = 4
		itemHeight = 0.11
		self.models['existing_lvl_scroller'] = MenuScroller(
		    self.parent, 'existing_lvl_scroller', path, '.lvl', (0.0, 0.7, -0.05, 0.59), (-0.35, 0, -0.5),
			numItemsVisible,itemHeight, (-0.2, 0.2, -0.37, 0.11), (0.35, 0, 0.4), self.onClick
		)
	'''
	### Name: goBack
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: goes back to the previous GUI
	'''
	def goBack(self):
		self.hide()
		self.gameMaker.models['background'].show()
		self.gameMaker.models['logo'].show()
		self.gameMaker.models['create_character_button'].show()
		self.gameMaker.models['create_object_button'].show()
		self.gameMaker.models['create_level_button'].show()
		self.gameMaker.models['back_button'].show()
	### Level Editor Commands Start Here
	'''
	### Name: startLevelEdit
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: Starts the actual Level editing functions
	'''
	def startLevelEdit(self):
		#Register button commands
		'''
		### Button Commands
		up-arrow: height+
		down-arrow: height-
		left-arrow: width-
		right-arrow: width+

		a: item back
		d: item forward
		w: move object "back" (into the screen)
		s: move object "forward" (out of the screen)

		left click: depends on whether placing or selecting

		delete: deletes object if selected
		'''
		self.hide()
		self.models['file_menu_gui'].show()
		self.curObject = VisualObject(self.parent, self.parent.render, 'mouseFollowObject')
		
		self.editorGUI = None
		
		self.parent.accept("arrow_up", self.moveSelectedObject, [0, 0.1])
		self.parent.accept("arrow_down", self.moveSelectedObject, [0, -0.1])
		self.parent.accept("arrow_left", self.moveSelectedObject, [-0.1, 0])
		self.parent.accept("arrow_right", self.moveSelectedObject, [0.1, 0])
		
		#Dictionaries
		self.keys = {"toggleModes" : True, "toggleMainCharacter" : False}
		self.levelObjects = dict()
		self.numObjInLevel = 0
		for key in self.level.objects.keys():
			self.levelObjects[key] = self.level.objects[key].exportToVisualObject(self.parent, self.parent.render)
			objNum = int(key.split('obj')[1])
			if (objNum > self.numObjInLevel):
				self.numObjInLevel = objNum
				
		if (self.level.mainCharacter):
			self.mainCharacter = self.level.mainCharacter.exportToVisualObject(self.parent, self.parent.render)
		else:
			self.mainCharacter = None
		
		#Keyboard Commands
		self.parent.accept("a",self.cycleItems,[-1])
		self.parent.accept("d",self.cycleItems,[1])
		self.parent.accept("w",self.changeDepth, [-1])
		self.parent.accept("s",self.changeDepth, [1])
		self.parent.accept("mouse1", self.pick)
		self.parent.accept("delete", self.deleteSelectedObject)
		
		self.parent.accept("space", self.toggleModes)
		self.parent.accept("c",self.addMainCharacter)
		#Tasks
		self.parent.taskMgr.add(self.mouseFollow, 'objectModelWatcher')
		
		self.getAllObjects()
		self.getAllMainCharacters()
		self.placingMainCharacter = False
		self.curObject.setObjectType(self.objects[0])
		self.selectedObject = None
		self.parent.cameraController.startMouseWatch()
		self.parent.cameraController.moveTo(x=0)
		
	'''
	### Name: getAllObjects
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: Retrieves all objects made by the user
	'''	
	def getAllObjects(self):
		objPath = "projects/" + self.projectDirectory + "/objects"
		path = os.path.abspath(objPath)
		#Get all objects
		for item in os.listdir(path):
			if(string.find(item, ".item") > -1):   #Make sure you get correct Items
				#Add all of the objects in the objects folder to the list of our objects.
				object = Object()
				object.load(objPath + "/" + item)
				self.objects.append(object)

	def getAllMainCharacters(self):
		mainCharPath = "projects/" + self.projectDirectory + "/characters/main"
		path = os.path.abspath(mainCharPath)
		print path
		#Get all objects
		for item in os.listdir(path):
			if(string.find(item, ".char") > -1):   #Make sure you get correct Items
				#Add all of the objects in the objects folder to the list of our objects.
				mainChar = MainCharacter()
				mainChar.load(mainCharPath + "/" + item)
				self.mainChars.append(mainChar)
		for item in self.mainChars:
			print item
			
	def toggleModes(self):
		if(self.keys["toggleModes"] ):
			self.keys["toggleModes"] = False
		else:
			self.keys["toggleModes"] = True
	
	'''
	### Name: addMainCharacter
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description:
	'''	
	def addMainCharacter(self):
		if(self.keys["toggleMainCharacter"] ):
			self.keys["toggleMainCharacter"] = False
		else:
			self.keys["toggleMainCharacter"] = True
		print self.keys["toggleMainCharacter"]
	
	'''
	### Name: cycleItems
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description:
	'''
	def cycleItems(self, val):
		if(self.keys["toggleModes"] and not self.keys["toggleMainCharacter"] ):
			self.curObjIndex = (self.curObjIndex + val) % len(self.objects)
			self.curObject.setObjectType(self.objects[self.curObjIndex])
		elif(self.keys["toggleMainCharacter"] and  not self.keys["toggleModes"]):
			self.curObjIndex = (self.curObjIndex + val) % len(self.mainChars)
			print self.mainChars[self.curObjIndex]
			self.curObject.setObjectType(self.mainChars[self.curObjIndex])
		
			
	'''
	### Name: changeDepth
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: 
	'''
	def changeDepth(self, val):
		if (self.selectedObject):
			self.selectedObject.setY(self.selectedObject.getY() + val)
	
	'''
	### Name: pick
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: handles the pick event for clicking
	'''
	def pick(self):
		cEntry = self.picker.pick(traverseRoot=self.plane)
		if cEntry:
			pos = cEntry.getSurfacePoint(render)
			self.numObjInLevel += 1
			newObj = VisualObject(self.parent, self.parent.render, 'obj' + str(self.numObjInLevel))
			newObj.setObjectType(self.curObject.object)
			newObj.setPos(pos + Vec3(0,-0.00001,0) )
			print "placing main: " + str(self.placingMainCharacter)
			if (not self.placingMainCharacter):
				self.levelObjects['obj' + str(self.numObjInLevel)] = newObj
				self.selectObject(newObj)
			else:
				self.selectObject(newObj)
				if (self.mainCharacter):
					self.mainCharacter.removeNode()
				self.mainCharacter = newObj
				
	'''
	### Name: mouseFollow
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: handles the pick event for mouse
	###				 movement
	'''
	def mouseFollow(self, task):
		if(self.keys["toggleModes"] and not self.keys["toggleMainCharacter"]):  # Add Object Task
			cEntry = self.picker.pick(traverseRoot=self.plane)
			if cEntry:
				pos = cEntry.getSurfacePoint(render)
				self.curObject.setPos(pos)
				self.placingMainCharacter = False
			return task.cont
		elif(not self.keys["toggleModes"] and not self.keys["toggleMainCharacter"]): # Selection Mode Task
			self.curObject.hide()
			self.parent.ignore("mouse1")
			self.parent.accept("mouse1", self.selectionPick)
			self.parent.taskMgr.add(self.selectionTask, 'selectTask')
			self.placingMainCharacter = False
			return task.done
		elif(( self.keys["toggleModes"] and self.keys["toggleMainCharacter"] ) or (not self.keys["toggleModes"] and self.keys["toggleMainCharacter"] )):		
			#self.curObject.hide()
			self.curObjIndex = 0
			self.curObject.setObjectType(self.mainChars[self.curObjIndex])
			#self.parent.ignore("mouse1")
			self.placingMainCharacter = True
			self.parent.taskMgr.add(self.characterAddTask, 'characterAddTask')
			return task.done
			
	'''
	### Name: selectionTask
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: Handles the selection mode task for
	###              our game
	'''
	def selectionTask(self,task):
		if(not self.keys["toggleModes"] and not self.keys["toggleMainCharacter"] ):		#Selection Mode Task
			#print "In Select Task"
			self.placingMainCharacter = False
			return task.cont
		elif(self.keys["toggleModes"] and not self.keys["toggleMainCharacter"]):	#Add Object Task
			self.curObject.show()
			self.parent.ignore("mouse1")
			self.parent.accept("mouse1", self.pick)
			self.curObjIndex = 0
			self.curObject.setObjectType(self.objects[self.curObjIndex])
			self.parent.taskMgr.add(self.mouseFollow, 'objectModelWatcher')
			self.placingMainCharacter = False
			return task.done
		elif(( self.keys["toggleModes"] and self.keys["toggleMainCharacter"] ) or (not self.keys["toggleModes"] and self.keys["toggleMainCharacter"] )):			#Add Main Character Task
			self.curObject.show()
			self.curObjIndex = 0
			self.curObject.setObjectType(self.mainChars[self.curObjIndex])
			self.parent.ignore("mouse1")
			self.parent.accept("mouse1", self.pick)
			self.parent.taskMgr.add(self.characterAddTask, 'characterAddTask')
			self.placingMainCharacter = True
			return task.done
	
	'''
	### Name: characterAddTask
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: Handles the selection mode task for
	###              our game
	'''
	def characterAddTask(self,task):
		if(( self.keys["toggleModes"] and self.keys["toggleMainCharacter"] ) or (not self.keys["toggleModes"] and self.keys["toggleMainCharacter"] )):				#Add Main Character Task
			cEntry = self.picker.pick(traverseRoot=self.plane)
			if cEntry:
				pos = cEntry.getSurfacePoint(render)
				self.curObject.setPos(pos)
				self.placingMainCharacter = True
			return task.cont
		elif(self.keys["toggleModes"] and not self.keys["toggleMainCharacter"]):	#Add Object Task
			self.parent.ignore("mouse1")
			self.parent.accept("mouse1", self.pick)
			self.curObjIndex = 0
			self.curObject.setObjectType(self.objects[self.curObjIndex])
			self.parent.taskMgr.add(self.mouseFollow, 'objectModelWatcher')
			self.placingMainCharacter = False
			return task.done
		elif(not self.keys["toggleModes"] and not self.keys["toggleMainCharacter"] ):	#Selection Mode Task
			self.curObject.hide()
			self.parent.ignore("mouse1")
			self.parent.accept("mouse1", self.selectionPick)
			self.parent.taskMgr.add(self.selectionTask, 'selectTask')
			self.placingMainCharacter = False
			return task.done
	'''
	### Name: pick
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: handles the pick event for clicking
	'''
	def selectionPick(self):
		cEntry = self.selectPicker.pick()
		if cEntry:
			geomHit = cEntry.getIntoNodePath()
			geom = geomHit.getParent()
			tag = geom.getTag('key')
			
			while geom and tag == "":
				tag = geom.getParent().getTag('key')
				geom = geom.getParent()

			if (tag):
				if (self.levelObjects.has_key(tag)):
					self.selectObject(self.levelObjects[tag])
				else:
					self.selectObject(self.mainCharacter)
				
	'''
	### Name: selectObject
	### Author: Andrew Oberlin, Patrick Delaney
	### Parameters: None
	### Description: selects the given object and creates a gui
	###              for editing its properties in the space
	'''
	def selectObject(self, object):
		if (self.selectedObject):
			self.selectedObject.setColor(1, 1, 1, 1)
		self.selectedObject = object
		object.setColor(102.0/255.0, 190.0/255.0, 183.0/255.0, 1)
		if (self.editorGUI):
			self.editorGUI.destroy()
		self.editorGUI = LevelObjectEditingGUI(self.parent, object)
	
	'''
	
	'''
	def deleteSelectedObject(self):
		if (self.selectedObject):
			self.selectedObject.removeNode()
			print "before: " + str(self.levelObjects.keys())
			del self.levelObjects[self.selectedObject.getKey()]
			print "\nafter: " + str(self.levelObjects.keys())
			self.selectedObject = None
	
	
	'''
	
	'''
	def moveSelectedObject(self, x, z):
		if (self.selectedObject):
			self.selectedObject.setX(self.selectedObject.getX() + x)
			self.selectedObject.setZ(self.selectedObject.getZ() + z)
		
	'''
	
	'''
	def cleanupLevel(self):
		for key in self.levelObjects.keys():
			self.levelObjects[key].removeNode()
		
		self.objects = []
		self.levelObjects = dict()
		
		self.parent.ignore("a")
		self.parent.ignore("d")
		self.parent.ignore("w")
		self.parent.ignore("s")
		self.parent.ignore("delete")
		self.parent.ignore("mouse1")
		self.parent.ignore("space") 
		
		self.curObject.removeNode()
		self.parent.taskMgr.remove('selectTask')
		
		self.show()
		self.models['file_menu_gui'].hide()
		self.level.setObjects({})
		if (self.editorGUI):
			self.editorGUI.destroy()
			
		self.parent.cameraController.endMouseWatch()
		
	'''
		Saves the current level
	'''
	def saveLevel(self):	
		if (self.level and self.mainCharacter):
			if (self.mainCharacter.collisionType):
				self.level.setObjects(self.levelObjects)
				self.level.setMainCharacter(self.mainCharacter)
				self.level.save(self.projectDirectory)
			else:
				dialog = Dialog(self.parent, 'error_dialog')
				dialog.addTextBox('Sorry! Your character needs collision detection!')	
				dialog.addOkCancel(dialog.hide, only='ok')
		else:
			dialog = Dialog(self.parent, 'error_dialog')
			dialog.addTextBox('Sorry! You need a main character to save this level!')	
			dialog.addOkCancel(dialog.hide, only='ok')